package lol.clann.minecraft.springboot.api.context;

import com.google.auto.service.AutoService;
import com.ruoshui.utils.java.exception.ExceptionUtils;
import lol.clann.minecraft.springboot.api.spi.sbp.ApplicationRunListener;
import lombok.Getter;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 保存一些spring的顶级对象
 *
 * 并提供创建非单例bean的接口
 *
 * @author pyz
 * @date 2019/5/7 8:26 PM
 */
@AutoService(ApplicationRunListener.class)
public class SpringContext implements ApplicationRunListener {

    @Getter
    private static SpringApplication springApplication;
    @Getter
    private static ConfigurableApplicationContext applicationContext;
    @Getter
    private static ConfigurableEnvironment applicationEnvironment;
    @Getter
    private static ConfigurableListableBeanFactory beanFactory;

    /**
     * 注册bean到beanfactory
     * @param beanName
     * @param beanDefinition
     */
    public static void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        ((DefaultListableBeanFactory) SpringContext.getBeanFactory()).registerBeanDefinition(beanName, beanDefinition);
    }

    /**
     * Return the bean instance that uniquely matches the given object type, if any.
     * <p>This method goes into {@link ListableBeanFactory} by-type lookup territory
     * but may also be translated into a conventional by-name lookup based on the name
     * of the given type. For more extensive retrieval operations across sets of beans,
     * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}.
     * @param requiredType type the bean must match; can be an interface or superclass
     * @return an instance of the single bean matching the required type
     * @throws NoSuchBeanDefinitionException if no bean of the given type was found
     * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
     * @throws BeansException if the bean could not be created
     * @since 3.0
     * @see ListableBeanFactory
     *
     * 如果要获取一个bean,就用这个
     * 若不存在,会创建,但不会执行依赖注入
     */
    public static <T> T getBean(Class<T> requiredType) {
        return SpringContext.getBeanFactory().getBean(requiredType);
    }

    /**
     * Return an instance, which may be shared or independent, of the specified bean.
     * <p>Allows for specifying explicit constructor arguments / factory method arguments,
     * overriding the specified default arguments (if any) in the bean definition.
     * @param name the name of the bean to retrieve
     * @param args arguments to use when creating a bean instance using explicit arguments
     * (only applied when creating a new instance as opposed to retrieving an existing one)
     * @return an instance of the bean
     * @throws NoSuchBeanDefinitionException if there is no such bean definition
     * @throws BeanDefinitionStoreException if arguments have been given but
     * the affected bean isn't a prototype
     * @throws BeansException if the bean could not be created
     * @since 2.5
     * 若要获取或者提前加载BeanFactory中已注册的bean,用这个
     */
    public static <T> T getBean(String name, Object... args) {
        return (T) SpringContext.getBeanFactory().getBean(name, args);
    }

    public static <T> T createBean(Class<T> type, Object[] constructorArgs) {
        constructorArgs = constructorArgs == null ? new Object[0] : constructorArgs;
        Constructor<?> c = null;
        for (Constructor<?> constructor : type.getConstructors()) {
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            if (parameterTypes.length != constructorArgs.length) {
                continue;
            }
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                Object arg = constructorArgs[i];
                if (!ClassUtils.isAssignableValue(parameterType, arg)) {
                    continue;
                }
            }
            c = constructor;
            break;
        }
        if (c == null) {
            throw new BeanCreationException(String.format("can not create %s bean with args %s", type.getName(), Arrays.toString(constructorArgs)));
        }
        c.setAccessible(true);
        Object bean;
        try {
            bean = c.newInstance(constructorArgs);
        } catch (Throwable e) {
            ExceptionUtils.throwException(e);
            throw new RuntimeException();
        }
        SpringContext.getBeanFactory().autowireBean(bean);
        return (T) bean;
    }

    public static <T> T createBean(Class<T> type) {
        return (T) SpringContext.getBeanFactory().createBean(type, AutowireCapableBeanFactory.AUTOWIRE_NO, true);
    }

    /**
     * 扫描资源文件
     *
     * @param locationPatterns
     * @return
     * @throws IOException
     */
    public static List<Resource> findResources(String... locationPatterns) throws IOException {
        List<Resource> resources = new ArrayList<>();
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(springApplication.getResourceLoader() == null ? new DefaultResourceLoader() : springApplication.getResourceLoader());
        for (String locationPattern : locationPatterns) {
            resources.addAll(Arrays.asList(resolver.getResources(locationPattern)));
        }
        return resources;

    }

    /**
     * 按order升序触发
     *
     * @return
     */
    @Override
    public int order() {
        return Integer.MIN_VALUE;
    }

    @Override
    public void starting(SpringApplication application) {
        SpringContext.springApplication = application;
    }

    @Override
    public void environmentPrepared(SpringApplication application, ConfigurableEnvironment environment) {
        SpringContext.applicationEnvironment = environment;
    }

    @Override
    public void contextPrepared(SpringApplication application, ConfigurableApplicationContext context) {
        SpringContext.applicationContext = context;
        SpringContext.beanFactory = context.getBeanFactory();
    }

}
